    "use strict";

    /**
     *  Delegate:
     *  Author:Vicco Wang Date: 2017.09.08
     *
     *  Description:
     *  该代理模式目的是为了跨代码执行方法. 比如我要在A控制器下调用B控制器下的某个方法.
     *  该方法实现结果和Angular的广播类似.但不需要绑定作用域,也不需要从根作用域进行轮询;
     *
     *  How to use:
     *  假设要在代码A中调用代码B中的某个方法,可以做如下操作:
     *
     *  1.首先需要在全局或 A,B的父域中实例化Delegate,假设命名为:
     *     var HomeDelegate = new Delegate()
     *  2.在B中注册需要被A调用的方法, 使用
     *      HomeDelegate.registerDelegate(name,type,fn)
     *    注册一个名称,然后方法类型,以及被调用的方法名, 类型名称与方法名称一一对应;
     *  3.在A中使用
     *      HomeDelegate.run(name,type)
     *    来调用方法,只需要填入对应的名称和方法类型即可;
     *
     *  4.你可以在A和B中检测某个名称或类型的方法是否已经被注册,使用
     *      HomeDelegate.isDelegateRegistered(name,[type])
     *      进行检测,如果只填写了名称,则只会检测名称,不会检测类型;
     *
     *  注意:
     *  1. 可以在同一个名称下注册不同类型的方法;比如我在B中可以注册名称都为"B"的"update","create","delete";
     *  2. 同一名称下的方法只能被注册一次,不能被覆盖,因为覆盖可能导致其他已经调用该方法的地方执行出现问题;
     *  3. 代理方法可以通过
     *     HomeDelegate.removeDelegate(name,[type])
     *     进行删除, 只需要填入名称和类型即可;
     *     如果你只填入了名称, 则该名称下的所有类型方法都会被移除;
     *
     * @return Delegate
     */

    var Delegate = function () {

        var delegateObj = {};
        /**
         *
         * @param name  {String}  _identify 唯一标识
         * @param type  {String}   类型名称,如 update, create等,自定义
         * @param fn    {Function} 需要调用的方法
         */
        this.registerDelegate = function (name, type, fn) {
            //代理中压根没有name 这个属性的对象
            if (!delegateObj.hasOwnProperty(name)) {
                var typeObj = {};
                typeObj[type] = fn;
                delegateObj[name] = typeObj;
                //代理中如果有name 属性的对象
            } else {
                //
                var oldNameObj = delegateObj[name];
                //如果该name属性的对象中没有为type的属性,则增加
                if (!oldNameObj.hasOwnProperty(type)) {
                    oldNameObj[type] = fn;
                } else {
                    console.warn("你不能覆盖注册同一个名称下的同一个代理方法.")
                }

            }

        };
        /**
         *
         * @param name          {String}  _identify 唯一标识
         * @param type          {String}   类型名称,如 update, create等,自定义
         * @param params        { Any }   在调用代理方法时传递的参数,可以是任意值或对象;
         * @returns {string}
         */
        this.run = function (name, type, params) {
            if (!name || !type)  return;
            if (delegateObj.hasOwnProperty(name)) {
                var nameObj = delegateObj[name];
                if (nameObj.hasOwnProperty(type)) {
                    if (typeof(nameObj[type]) === "function") {
                        nameObj[type].call(null, params);
                    }
                } else {
                    console.warn("没找到该名称下的代理方法,[name: " + name + " type: " + type + "]");
                }
            } else {
                console.warn("没找到你要执行的代理名称,[name: " + name + " type: " + type + "]")

            }
            return 'no delegate method has ran';
        };
        /**
         *
         * @param name
         * @param type
         * @returns {boolean}
         */
        this.isDelegateRegistered = function (name, type) {
            if (name && !type) return delegateObj.hasOwnProperty(name);
            if (delegateObj.hasOwnProperty(name)) {
                var nameObj = delegateObj[name];
                return nameObj.hasOwnProperty(type);
            } else {
                return false;
            }
            return false;
        };
        /**
         *
         * @param name
         * @param type
         */
        this.removeDelegate = function (name, type) {
            if (name && !type) {
                if (this.isDelegateRegistered(name)) delete delegateObj[name];
            }
            if (this.isDelegateRegistered(name, type)) {
                var nameObj = delegateObj[name];
                delete nameObj[type];
            }
        };
    };

    window.HomepageDelegate = new Delegate();

    var homepage = angular.module('zhx.homepage', ['gridstack-angular', 'ui.bootstrap.contextMenu']);

    /**
     * zhx.homepage plugin
     * Author: Vicco Wang
     * Date: 2017/06/22
     *
     *
     * description:
     * 1. 该项目着重依赖gridStack组件实现拖拽与排序功能,着重依赖oclazyLoad组件实现动态Angular模块绑定功能与依赖懒加载功能;
     * 2. 每一个组件必须定义的字段包含
     * @id                  用于判定唯一性;
     * @moduleName          使用oclazyLoad动态绑定Angular模块的模块名称
     * @moduleDisplayName   对应组件显示的标题名称
     * @templateUrl         懒加载模块的模板路径{相对路径}
     * @style               组件样式,目前设置的是Header部分的背景色
     * @width               最大值12(该最大值不可配置), 表示组件占用的列宽
     * @height              最大值7(该最大值可配置), 表示组件占用行高(行高可配置)
     * 3. 每一个组件会有额外一些属性,包含:
     * @added               是否已被添加至主页中
     * @autoPosition        是否自动布局
     * @minHeight           最小高度
     * @maxHeight           最大高度
     *
     * 组件的数据结构应该是类似如下:
     * {
     *  "id" : "abc1234567890",
     *  "moduleName": "someModule",
     *  "moduleDisplayName" : "测试模块",
     *  "templateUrl" : "modules/tpl/someModule.html",
     *  "style" : "normal",
     *  "width" : 5,
     *  "height" : 3
     * }
     *
     *
     * dependencies:
     * - angularjs 1.x
     * - lodash
     * - gridStack          --https://github.com/troolee/gridstack.js
     * - oc.lazyload
     * - ui.bootstrap.contextmenu
     * - bootstrap ui
     *
     *
     * usage:
     *
     * 模板中定义:
     * <zhx-homepage options="homeOptions"></zhx-homepage>
     *
     * 控制器中需要定义:
     * $scope.homeOptions, 该字段可以配置如下属性:
     * @data            当前用户已经添加在主页中的组件的数据对象;
     * @allWidgetData   当前用户拥有的全部组件的数据对象, PS: 全部组件就包含已被添加在主页中的和未被添加的,只要是拥有添加权限的都会在该对象中;
     *
     * 需要在module的config配置项中定义: zhxHomepageProvider 对象
     * 该对象用于初始化一些基础内容;目前可以配置的内容为:
     * @updateWidgetURL     表示用户每次修改,移除,拖拽,缩放等影响到当前已添加组件数据时,会通过该URL地址提交数据对象;
     *
     * 模块数据需要通过oclazyLoad来定义(这里的定义方式和zhx.tab.frame中的模块定义方式完全一致),
     * 这里需要通过$ocLazyLoadProvider的config配置项来配置模块,例如:
     * $ocLazyLoadProvider.config({
     *      modules : [
     *          {
     *              name : "someModule",
     *              files : ["tpl/module-controller.js"]
     *          }
     *      ]
     * });
     *
     * 组件中必须实现 刷新的代理方法,组件才会拥有刷新功能,如果未实现,则组件的刷新为不可用状态
     *
     *
     *
     *
     */
    homepage.provider('zhxHomepage', function () {

        var config = {
            debug: false,
            autoSave: true,
            delegateConfig: {
                update: 'update',
                resize: 'resize'
            },
            updateWidgetURL: ''
        };

        this.config = function (userCfg) {
            config = angular.merge({}, config, userCfg);
        };

        this.$get = function () {
            return {
                getConfig: function () {
                    return config;
                }
            }
        }

    });

    /**
     *
     */
    homepage.factory('zhx.homepage.util', ['$q', '$http', 'zhxHomepage', function ($q, $http, zhxHomepageProvider) {
        var service = {};

        /**
         * 将最初始数据新增一些针对拖拽的配置属性;
         * @param widgetData
         * @returns {*}
         */
        service.rebuildWidgetData = function (widgetData) {
            /**
             * 为每一个组件新增三个属性,这三个属性用于组件控制,
             * 默认每个组件最小高度3行,最大高度为7行
             * @type {{autoPosition: boolean, minHeight: number, maxHeight: number}}
             */
            var extraWidgetParams = {
                // autoPosition: false,
                minHeight: 3,
                maxHeight: 7
            };

            for (var i = 0; i < widgetData.length; i++) {
                var widget = widgetData[i];
                Object.assign(widget, extraWidgetParams);
            }

            return widgetData;
        };

        /**
         * 根据ID查找组件
         * @param widgets
         * @param id
         * @returns {*}
         */
        service.findWidgetById = function (widgets, id) {
            for (var i = 0; i < widgets.length; i++) {
                if (widgets[i].id === id) return widgets[i];
            }
            return false;
        };

        /**
         * 保存布局
         * TODO 假操作
         */
        service.saveChanges = function (widgetsData) {
            var url = zhxHomepageProvider.getConfig().updateWidgetURL;
            return $http.get(url).then(function () {

            });
        };

        /**
         * 移除一个组件
         * @param widgets
         * @param widget
         */
        service.removeWidget = function (widgets, widget) {
            //TODO 假操作
            var deferred = $q.defer();
            for (var i = 0; i < widgets.length; i++) {
                if (widgets[i].id === widget.id) {
                    widgets.splice(i, 1);
                    break;
                }
            }
            deferred.resolve(widgets);
            return deferred.promise;
        };

        /**
         * 根据页面已经载入的组件来配置用户权限下全部组件的属性;
         * 这里为全部组件添加一个"是否已添加至组件"的属性--added
         * @param allWidgets
         * @param widgets
         */
        service.constructAllWidgetsFromWidgets = function (allWidgets, widgets) {
            var deferred = $q.defer();
            for (var i = 0; i < allWidgets.length; i++) {
                var widget = allWidgets[i];
                widget.added = false;
                for (var j = 0; j < widgets.length; j++) {
                    if (widget.id === widgets[j].id) {
                        //是否已添加至主页 状态
                        widget.added = true;
                        break;
                    }
                }
            }
            deferred.resolve({
                allWidgets: allWidgets,
                widgets: widgets
            });
            return deferred.promise;
        };

        return service;

    }]);

    /**
     *
     */
    homepage.controller('zhx.homepage.mainCtrl', ['$scope', '$q', '$timeout', 'zhxHomepage', 'zhx.homepage.util', function ($scope, $q, $timeout, zhxHomepageProvider, util) {
        //主页系统全局配置项
        $scope.homepageOptions = zhxHomepageProvider.getConfig();
        //已添加至页面的插件数据对象
        $scope.widgets = [];
        //该用户拥有的全部widgets;
        $scope.allWidgetData = [];
        //组件样式
        $scope.widgetStyle = ['red', 'blue', 'green', 'purple', 'orange', 'normal'];
        /**
         * getDataDeferred  已添加widgets deferred
         * getAllWidgetsDataDeferred 当前用户所有widgets deferred
         */
        var getDataDeferred = $q.defer(), getAllWidgetsDataDeferred = $q.defer();
        //插件是否在页面中有所变动
        $scope.widgetsChanged = false;
        //保存布局状态, true为正在保存
        $scope.saveStatus = false;
        //是否开启了自动保存功能
        $scope.isAutoSave = zhxHomepageProvider.getConfig().autoSave;

        /**
         * Grid stack 配置项
         * @type {{verticalMargin: number, handle: string}}
         */
        $scope.gridStackOptions = {
            // animate : true,
            verticalMargin: 10,
            handle: '.grid-stack-item-header > .item-header-title'
        };

        /**
         * 监控主页读取的已添加widgets数据
         */
        var clearWidgetsWatch = $scope.$watch('options.data', function (widgetData) {
            if (angular.isArray(widgetData)) {
                getDataDeferred.resolve(widgetData);
                clearWidgetsWatch();
            }
        });

        /**
         *
         * @type {*}
         */
        var clearAllwidgetsWatch = $scope.$watch('options.allWidgetData', function (allWidgets) {
            if (angular.isArray(allWidgets)) {
                getAllWidgetsDataDeferred.resolve(allWidgets);
                clearAllwidgetsWatch();
            }
        });

        /**
         * 为组件绑定 变化后触发保存按钮展示的watch,因为需要监控所有属性的变动,
         * 因此需要深度监控 set true;
         */
        getDataDeferred.promise.then(function () {
            var saveDelay;
            $scope.$watch('widgets', function (nv, ov) {
                if (nv && nv.length && ov && ov.length) {
                    $scope.widgetsChanged = !angular.equals(nv, ov);
                    //必须是自动保存打开以及当前未进行保存,才会做TIP动效
                    if ($scope.isAutoSave && !$scope.saveStatus) {
                        $scope.saveStatus = true;

                        /**
                         * 做一个弹出动效
                         */
                        angular.element('.auto-save-wrapper').velocity({
                            width: 150,
                            marginLeft: -75,
                            opacity: .8
                        }, {
                            duration: 700,
                            easing: [250, 15]
                        });
                    }
                    /**
                     * 自动保存开启则直接saveChange;
                     */
                    if ($scope.isAutoSave) {
                        //保存布局
                        util.saveChanges($scope.widgets).then(function () {
                            //如果有正在保存的状态,则暂时先不关闭弹出TIP,
                            if (saveDelay) $timeout.cancel(saveDelay);
                            //延迟一会儿再关闭弹出TIP,保证连续多次保存时,TIP始终在页面上展示而不会来回隐藏/显示闪烁
                            saveDelay = $timeout(function () {
                                var saveDom = angular.element('.auto-save-wrapper');
                                saveDom.velocity({
                                    width: 0,
                                    marginLeft: 0,
                                    opacity: 0
                                }, {
                                    duration: 700,
                                    easing: [250, 15],
                                    complete: function (dom) {
                                        //插件是否在页面中有所变动
                                        $scope.widgetsChanged = false;
                                        //保存布局状态, true为正在保存
                                        $scope.saveStatus = false;
                                    }
                                });
                            }, 2000);
                        });
                    }
                }
            }, true);
        });

        /**
         * 当数据全部初始化完成后,对当前用户所有可使用的widgets数据进行重构
         */
        $q.all([getDataDeferred.promise, getAllWidgetsDataDeferred.promise]).then(function (result) {
            //赋值当前已添加widgets数据对象
            $scope.widgets = util.rebuildWidgetData(result[0]);
            //重构allWidgets数据对象
            var widgets = angular.copy(result[0]);
            var allInWidgets = angular.copy(result[1]);
            util.constructAllWidgetsFromWidgets(allInWidgets, widgets).then(function (res) {
                $scope.allWidgetData = res.allWidgets;
            });
        });

    }]);

    /**
     *
     */
    homepage.directive('zhxHomage', ['$timeout', '$http', '$ocLazyLoad', 'zhxHomepage', 'zhx.homepage.util', function ($timeout, $http, $ocLazyLoad, zhxHomepageProvider, util) {
        return {
            restrict: 'E',
            scope: {
                options: '='
            },
            replace: true,
            controller: 'zhx.homepage.mainCtrl',
            templateUrl: 'zhx.homepage.tpl.vicco',
            link: function (scope, tElement, tAttrs) {

                //Homepage代理KEY配置项
                var delegateConfig = scope.homepageOptions.delegateConfig;

                //TODO 数据完整性校验, 应先update数据
                scope.addWidget = function (event, widget) {
                    var ev = event || window.event;
                    ev.stopPropagation();
                    //保证页面上添加的组件唯一性
                    var isExist = util.findWidgetById(scope.widgets, widget.id);
                    if (isExist) return;

                    /**
                     * 为新增的组件增加三个默认属性
                     */
                    var w = Object.assign(widget, {
                        minHeight: 3,
                        maxHeight: 7,
                        added: true,
                        autoPosition: true
                    });
                    //复制一份,保证添加进主页的组件不论修改删除不会影响allWidgetsData
                    scope.widgets.push(angular.copy(w));

                };

                //TODO removeWidget
                scope.removeWidget = function (widget) {
                    //copy a widgets object to handle this;
                    var widgets = angular.copy(scope.widgets);
                    return util.removeWidget(widgets, widget).then(function (res) {
                        scope.widgets = res;
                        return res;
                    }).then(function (w) {
                        var allInWidgets = angular.copy(scope.allWidgetData);
                        util.constructAllWidgetsFromWidgets(allInWidgets, w).then(function (res) {
                            scope.allWidgetData = res.allWidgets;
                        });
                    });
                };

                /**
                 * 隐藏组件列表框
                 * @param target
                 * @private
                 */
                var _hideWidgetSelect = function (target) {
                    var dom = target.length ? target : target.data.dom;
                    if (target.data) {
                        if (angular.element(target.target).parents('.zhx-homepage-all-widgets').length) return;
                    }
                    dom.velocity({
                        height: 0
                    }, {
                        easing: "easeInSine",
                        duration: 300,
                        complete: function (dom) {
                            angular.element(dom).hide()
                        }
                    });
                    angular.element('body').off('click', _hideWidgetSelect);
                };

                /**
                 * 打开组件列表弹出框
                 * @param event
                 */
                scope.openWidgets = function (event) {
                    var ev = event || window.event;
                    ev.stopPropagation();
                    var dom = angular.element('.zhx-homepage-all-widgets'),
                        domVisible = dom.is(":visible");
                    if (domVisible) {
                        _hideWidgetSelect(dom);
                        return;
                    }
                    var widgetHeight = scope.allWidgetData.length * 42;
                    dom.show().velocity({
                        'height': widgetHeight + 80
                    }, {
                        easing: "spring",
                        duration: 1000,
                        complete: function (dom) {
                            angular.element('body').on('click', '', {dom: angular.element(dom)}, _hideWidgetSelect);
                        }
                    });
                };

                /**
                 * 保存页面布局
                 */
                scope.saveChanges = function () {
                    if (scope.saveStatus) return;
                    scope.saveStatus = true;
                    util.saveChanges(scope.widgets).then(function () {
                        $timeout(function () {
                            //重置保存按钮为隐藏状态
                            scope.widgetsChanged = false;
                            //重置保存状态
                            scope.saveStatus = false;
                        }, 300);
                    })

                };

                /**
                 * 单独Widget右上角菜单
                 * @type {[*]}
                 */
                scope.widgetMenu = [
                    ['修改标题', function ($itemScp) {
                        //将目前的标题存进正在修改的临时对象中
                        $itemScp.customControl.titleWillChange = true;
                        $itemScp.customControl.title = $itemScp.widget.moduleDisplayName;
                    }],
                    ['修改样式', function ($itemScp) {
                        //打开样式修改浮动框
                        $itemScp.customControl.styleWillChange = true;
                    }],
                    null,
                    ['刷新', function ($itemScp, $parentItem, event) {
                        HomepageDelegate.run($itemScp.widget.id, delegateConfig.update, $itemScp.widget);
                    }, function ($itemScp) {
                        //如果没有注册代理,则不能进行刷新操作
                        return HomepageDelegate.isDelegateRegistered($itemScp.widget.id, delegateConfig.update);
                    }],
                    // null,
                    ['移除', function ($itemScp, $parentItem, event) {
                        scope.removeWidget($itemScp.widget).then(function () {
                            HomepageDelegate.removeDelegate($itemScp.widget.id);
                        })
                    }]
                ];

                /**
                 * widget 内部交互事件
                 * @param event
                 */
                scope.onResizeStop = function (event) {
                    var widgetId = angular.element(event.target).attr('widget-id');
                    HomepageDelegate.isDelegateRegistered(widgetId, delegateConfig.resize) && HomepageDelegate.run(widgetId, delegateConfig.resize);
                };

            }
        }
    }]);

    /**
     * 为了避免修改组件自有属性,例如标题每次输入都会触发修改widget,而触发保存操作.
     * 这里指令会设置一个临时对象,用来保存修改时的内容,当确认保存时,才会将对象内容存入widget对象中
     */
    homepage.directive('gridstackCustomItem', ['$document', function ($document) {
        return {
            restrict: 'A',
            link: function (scope, tElement, tAttrs) {

                /**
                 * 保存数据临时对象,目前有标题和样式两种
                 * @type {{title: string}}
                 */
                scope.customControl = {
                    title: '',
                    style: scope.widget.style,
                    titleWillChange: false,
                    styleWillChange: false
                };

                /**
                 * 标题双击可进行标题的内容编辑;
                 */
                scope.showHeaderTitleInput = function (widget) {
                    //将当前widget的标题名称赋值给临时修改对象
                    scope.customControl.title = widget.moduleDisplayName;
                    //开启编辑框
                    scope.customControl.titleWillChange = true;
                };

                /**
                 * 在修改组件标题的input blur时触发修改;
                 * @param $event window.event
                 * @param widget
                 */
                scope.confirmModifyModuleTitle = function ($event, widget) {
                    //chrome && firefox 兼容
                    var ev = $event || window.event;
                    var keycode = ev.keyCode ? ev.keyCode : ev.which;
                    //只有回车和input失焦时保存
                    if ((ev.type === "keyup" && keycode === 13 ) || ev.type === "blur") {
                        //设置标题
                        if (widget.moduleDisplayName !== scope.customControl.title) {
                            widget.moduleDisplayName = scope.customControl.title;
                        }
                        scope.customControl.title = '';
                        scope.customControl.titleWillChange = false;
                    }

                };

                /**
                 * 修改样式
                 * @param style
                 */
                scope.changeStyle = function (style) {
                    scope.customControl.style = style;
                };

                /**
                 * style Hide event
                 * @param $event
                 * @private
                 */
                var _hideStyleDom = function ($event) {
                    var ev = $event || window.event;
                    if (ev.target.className.indexOf('grid-stack-item-style') !== -1 || angular.element(ev.target).parents('.grid-stack-item-style').length) {
                        return;
                    }
                    //finally hide the style dom and unbind the event
                    scope.$apply(function () {
                        //将临时的修改对象赋值给widget,以便保存修改
                        scope.widget.style = scope.customControl.style;
                        //隐藏style修改弹出框
                        scope.customControl.styleWillChange = false;
                        //取消全局事件绑定
                        angular.element('body').off('click', _hideStyleDom);
                    })
                };
                //监控是否开始了组件style修改
                scope.$watch('customControl.styleWillChange', function (canbeModified) {
                    if (canbeModified) {
                        //动画显示style修改浮动框
                        tElement.find(".grid-stack-item-style").velocity({
                            opacity: .9,
                            top: 40
                        }, {
                            display: 'flex',
                            easing: 'easeInOut',
                            duration: 300,
                            complete: function () {
                                angular.element('body').on('click', _hideStyleDom)
                            }
                        });
                    } else {
                        tElement.find(".grid-stack-item-style").velocity('reverse', {display: 'none'});
                    }
                });

            }
        }
    }]);

    /**
     * 一个自动focus的指令
     * when 'autoFocuse' is set to true{Bool}, then apply this input focus automatically
     */
    homepage.directive('autoFocus', function () {
        return {
            link: function (scope, tElement, tAttrs) {
                var isFocus = scope.$eval(tAttrs.autoFocus);
                isFocus && tElement.focus();
            }
        }
    });


    /**
     *
     */
    homepage.run(['$templateCache', function ($templateCache) {
        $templateCache.put('zhx.homepage.tpl.vicco',
            '<div class="zhx-homepage-wrapper">' +
            '<div class="zhx-homepage-header">' +
            '<div class="ctrl-button add" ng-click="openWidgets($event)">添加组件</div>' +
            '<div class="ctrl-button save" ng-if="widgetsChanged && !isAutoSave" ng-click="saveChanges()">{{ saveStatus ? "正在保存..." : "保存布局" }}</div>' +
            '</div>' +
            '<div class="zhx-homepage-main">' +
            '<div class="container-fluid">' +
            '<div class="row">' +
            //组件框架grid stack
            '<div gridstack ' +
            'class="grid-stack grid" ' +
            'options="gridStackOptions" ' +
            // 'on-change="onChange(event,items)" ' +
            // 'on-drag-start="onDragStart(event,ui)" ' +
            // 'on-drag-stop="onDragStop(event,ui)" ' +
            // 'on-resize-start="onResizeStart(event,ui)" ' +
            'on-resize-stop="onResizeStop(event,ui)" ' +
            '>' +
            //独立组件
            //这里必须有track by 防止当前widget更新后引起其他widget更新;
            '<div gridstack-item gridstack-custom-item ng-repeat="widget in widgets track by widget.id" ' +
            'widget-id="{{ ::widget.id }}" ' +
            'class="grid-stack-item {{ widget.style !== customControl.style ? customControl.style : widget.style }}" ' +
            'gs-item-x="widget.x" ' +
            'gs-item-y="widget.y" ' +
            'gs-item-width="widget.width" ' +
            'gs-item-height="widget.height" ' +
            'gs-item-min-height="widget.minHeight" ' +
            'gs-item-max-height="widget.maxHeight" ' +
            'gs-item-autopos="widget.autoPosition" ' +
            'on-item-added="onItemAdded(item)" ' +
            'on-item-removed="onItemRemoved(item)">' +
            //组件内容Wrapper
            '<div class="grid-stack-item-content" oc-lazy-load="widget.moduleName">' +
            //组件header
            '<div widget-header class="grid-stack-item-header">' +
            //组件名称
            '<div class="item-header-title" ng-dblclick="showHeaderTitleInput(widget)">{{ customControl.titleWillChange ? \'\' : widget.moduleDisplayName }}</div>' +
            //修改组件名称
            '<div class="item-header-title-input" ng-if="customControl.titleWillChange">' +
            '<input type="text" ng-model="customControl.title" auto-focus="customControl.titleWillChange" ng-blur="confirmModifyModuleTitle($event,widget)" ng-keyup="confirmModifyModuleTitle($event,widget)" />' +
            '</div>' +
            '<div class="item-header-ctrl">' +
            '<ul>' +
            '<li class="zhxHomepage icon-more" context-menu="widgetMenu" context-menu-on="click"></li>' +
            '</ul>' +
            '</div>' +
            '</div>' +
            '<div class="grid-stack-item-style">' +
            // 'style block , red, blue, green, normal' +
            '<ul>' +
            '<li class="{{ ::style }}" ng-click="changeStyle(style)" ng-repeat="style in ::widgetStyle"></li>' +
            '</ul>' +
            '</div>' +
            //组件include模块
            '<div widget-content ng-include src="widget.templateUrl" class="grid-stack-item-content-include"></div>' +
            '</div>' +
            '</div>' +
            '</div>' +
            '</div>' +
            '</div>' +
            '</div>' +
            '<div ng-if="homepageOptions.debug" class="zhx-homepage-debug">' +
            '<h4>DEBUG</h4>' +
            '<div>' +
            '{{modifyingModuleTitle}}' +
            '{{widgets}}' +
            '</div>' +
            '</div>' +
            '<div class="zhx-homepage-all-widgets">' +
            '<div class="all-widgets-header"><span>主页组件</span></div>' +
            '<ul>' +
            '<li ng-repeat="w in allWidgetData track by w.id">' +
            '<span class="widget-title" ng-bind="w.moduleDisplayName"></span>' +
            '<span class="widget-ctrl" ng-if="!w.added" ng-click="addWidget($event,w)">添加</span>' +
            '<span class="widget-ctrl" ng-if="w.added" ng-class="{ disabled : w.added }">已添加</span>' +
            '</li>' +
            '</ul>' +
            '</div>' +
            '<div ng-if="!widgets.length" class="empty-widgets">请先添加一个组件</div>' +
            '<div class="auto-save-wrapper"><span>自动保存布局...</span></div>' +
            '</div>'
        )
    }]);
